import { Matrix, Vector3 } from '@babylonjs/core/Maths/math.vector';
import { AbstractDragLimit } from './abstract-drag-limit';
import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
import { IDragEventData, IDragProgressEventData } from '../config/abstract-drag-config';
import { BoundingBox } from '@babylonjs/core/Culling/boundingBox';


/**
 * 拖动中的回调函数
 * @param target 对象
 * @param manual 是否手动
 */
interface IProgressCallback {
    (target: TransformNode, manual: boolean): void
}


/**
 * 约束在盒体（AABB3D）范围内拖动
 * @param min 下限值
 * @param max 上限值
 */
class DragBoxLimit extends AbstractDragLimit {

    private _min = new Vector3(-1, -1, -1);
    private _max = new Vector3(1, 1, 1);
    private _box = new BoundingBox(new Vector3(), new Vector3());
    private _tv0 = new Vector3();
    private _tv1 = new Vector3();
    private _useWorld = false;
    private _tm0 = new Matrix();
    private _localOfs = new Vector3();

    constructor(min: Vector3, max: Vector3) {
        super();
        this.setInformation(min, max);
    }

    public set useWorld(isWorld: boolean) {
        this._useWorld = isWorld;
    }

    public get limitType(): string {
        return 'boxLimit';
    }

    public setInformation(min: Vector3, max: Vector3): void {
        this._min.copyFrom(min);
        this._max.copyFrom(max);
    }

    public set onUpdate(func: IProgressCallback) {
        this.updateCall = func;
    }

    public extract() {
    }

    public update(target: TransformNode): void {
        if (this.updateCall) this.updateCall(target, false)
    }

    public setStart(target: TransformNode, eventData: IDragEventData): void {
        super.setStart(target, eventData);
        const wp = target.absolutePosition;
        this.startOffset.copyFrom(this.currentPosition).subtractInPlace(wp);

        if (this._useWorld || (!target.parent)) {
            this._localOfs.copyFrom(this.startOffset);
        } else {
            const inv = this._tm0.copyFrom(target.parent!.getWorldMatrix());
            inv.invert();
            const wp = target.position;
            this._tv1.copyFrom(this.currentPosition);
            Vector3.TransformCoordinatesToRef(this._tv1, inv, this._tv1);
            this._localOfs.copyFrom(this._tv1).subtractInPlace(wp);
        }
    }

    public setProgress(target: TransformNode, eventData: IDragProgressEventData): void {
        super.setProgress(target, eventData);
        const wp = this._tv0;
        const inv = this._tm0;
        if (this._useWorld || (!target.parent)) {
            wp.copyFrom(this.currentPosition);
        } else {
            inv.copyFrom(target.parent!.getWorldMatrix());
            inv.invert();
            Vector3.TransformCoordinatesToRef(this.currentPosition, inv, wp);
        }
        const fp = this._tv1.copyFrom(wp).subtractInPlace(this._localOfs);

        this._box.minimum.copyFrom(this._min);
        this._box.maximum.copyFrom(this._max);
        if (!this._box.intersectsPoint(fp)) {
            if (fp.x > this._max.x) fp.x = this._max.x;
            if (fp.y > this._max.y) fp.y = this._max.y;
            if (fp.z > this._max.z) fp.z = this._max.z;
            if (fp.x < this._min.x) fp.x = this._min.x;
            if (fp.y < this._min.y) fp.y = this._min.y;
            if (fp.z < this._min.z) fp.z = this._min.z;
            fp.addInPlace(this._localOfs);
        }

        if (this._useWorld || (!target.parent)) {
            this.currentPosition.copyFrom(fp);
        } else {
            inv.invert();
            Vector3.TransformCoordinatesToRef(fp, inv, this.currentPosition);
        }
        if (this.updateCall) this.updateCall(target, true, eventData);
    }
}


export { DragBoxLimit };